﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using MathematicallySafe.Base;
using MathematicallySafe.HeadCounter.Data.Interfaces;
using MathematicallySafe.HeadCounter.Services.Calculators;
using HeadCounter.Armourer;
using HeadCounter.Armourer.Base;
using MathematicallySafe.HeadCounter.Commands;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data.Models;
using MathematicallySafe.HeadCounter.Services;
using StructureMap;

namespace MathematicallySafe.HeadCounter.ViewModels
{
    public class RaiderDetailedViewModel : RaiderViewModel
    {
        private static readonly IOptions Options = ObjectFactory.GetInstance<IOptions>();
        private static readonly IGuild Guild = ObjectFactory.GetInstance<IGuild>();
        private static readonly IRaidHistory RaidHistory = ObjectFactory.GetInstance<IRaidHistory>();
        private static readonly ILootTable LootTable = ObjectFactory.GetInstance<ILootTable>(); 
        private static readonly IArmouryService ArmouryService = ObjectFactory.GetInstance<IArmouryService>();
        private static readonly ICalculationService Calculator = ObjectFactory.GetInstance<ICalculationService>();

        private DateTime _lastRaidDate;
        private List<RaiderRaidHistoryLine> _raidingHistory;

        private DelegateCommand _downloadArmouryData;
        private DelegateCommand<LootItem> _selectItemCommand;
        private DelegateCommand<LootItem> _selectItemBottomSlotCommand;

        public RaiderDetailedViewModel(Raider raider): base(raider)
        {
        }

        public string AltList
        {
            get
            {
                string altList = "";

                if (_raider.Characters.Count > 1)
                {
                    IEnumerable<Character> alts = from c in _raider.Characters
                                                  where c.Name != _raider.MainName
                                                  select c;

                    alts.ForEach(a => altList += a.Name + ", ");
                    altList = altList.Substring(0, altList.Length - 2);
                }
                else
                {
                    altList = "None";
                }

                return altList;
            }
        }

        #region Last Raid

        public DateTime LastRaidDate
        {
            get
            {
                if (_lastRaidDate == DateTime.MinValue)
                {
                    _lastRaidDate = FindLastRaidDate();
                }

                return _lastRaidDate;
            }
            set
            {
                _lastRaidDate = value;
                OnPropertyChanged("LastRaidDate");
            }
        }

        private DateTime FindLastRaidDate()
        {
            return (from raid in RaidHistory.Raids
                    where raid.RaiderIdentities.Contains(_raider.Identity)
                    select raid.Date).LastOrDefault();
        }

        private void ResetLastRaidDate()
        {
            _lastRaidDate = DateTime.MinValue;
        }

        #endregion

        #region Recent Raids

        private List<RaiderRaidSummary> _recentRaids;

        public IEnumerable<RaiderRaidSummary> RecentRaids
        {
            get
            {
                if (_recentRaids == null)
                {
                    _recentRaids = new List<RaiderRaidSummary>();

                    int lastRaidWeek = RaidHistory.LatestRaidWeek;

                    IEnumerable<Raid> recentRaids = from r in RaidHistory.Raids
                                                    orderby r.Date descending
                                                    where
                                                        r.WeekNumber >=
                                                        lastRaidWeek - Options.ShortPeriodLength &&
                                                        r.RaiderAttendedAnyChar(_raider.MainName)
                                                    select r;

                    recentRaids.ForEach(r => _recentRaids.Add(CreateRaidSummary(r)));
                }

                return _recentRaids;
            }
        }

        private RaiderRaidSummary CreateRaidSummary(Raid r)
        {
            IEnumerable<LootItem> lootWon = from l in r.Loot
                                            where l.Winner == _raider.Identity
                                            select l.Item;

            var raid = new RaiderRaidSummary
                       {
                           Date = r.Date,
                           Optional = r.Optional,
                           Location = r.Zone,
                           LootWon = new ObservableCollection<LootDropSummary>(),
                       };

            lootWon.ForEach(loot => raid.LootWon.Add(new LootDropSummary()
                                                     {
                                                         Item = loot,
                                                         Breakdown = loot.ValueCalculationDetails(_raider),
                                                         Value = loot.LootValue(_raider)
                                                     }));

            return raid;
        }

        private void ResetRecentRaids()
        {
            _recentRaids = null;
            OnPropertyChanged("RecentRaids");
        }

        #endregion

        #region Attendance & Metrics

        public double Attendance
        {
            get
            {
                switch (Options.DefaultPeriod)
                {
                    case Period.Short:
                        return _raider.ShortPeriodAttendance;

                    case Period.Long:
                        return _raider.LongPeriodAttendance;

                    case Period.Lifetime:
                        return _raider.LifetimeAttendance;

                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        public int LootCount
        {
            get
            {
                switch (Options.DefaultPeriod)
                {
                    case Period.Short:
                        return _raider.ShortPeriodLootCount;
                    case Period.Long:
                        return _raider.LongPeriodLootCount;
                    case Period.Lifetime:
                        return _raider.LifetimeLootCount;
                }

                return _raider.LifetimeLootCount;
            }
        }

        public double LootRate
        {
            get
            {
                switch (Options.DefaultPeriod)
                {
                    case Period.Short:
                        return _raider.ShortPeriodLootRate;
                    case Period.Long:
                        return _raider.LongPeriodLootRate;
                    case Period.Lifetime:
                        return _raider.LifetimeLootRate;
                }

                return _raider.LifetimeLootRate;
            }
        }

        public double? LootPriority
        {
            get
            {
                switch (Options.DefaultPeriod)
                {
                    case Period.Short:
                        return _raider.ShortPeriodLootPriority;
                    case Period.Long:
                        return _raider.LongPeriodLootPriority;
                    case Period.Lifetime:
                        return _raider.LifetimeLootPriority;
                }

                return _raider.LifetimeLootRate;
            }
        }

        public double? RelativeLootPriority
        {
            get
            {
                switch (Options.DefaultPeriod)
                {
                    case Period.Short:
                        return _raider.ShortPeriodRelativeLootPriorty;
                    case Period.Long:
                        return _raider.LongPeriodRelativeLootPriorty;
                    case Period.Lifetime:
                        return _raider.LifetimeRelativeLootPriorty;
                }

                return _raider.LifetimeLootRate;
            }
        }

        public double ShortPeriodAttendance
        {
            get { return _raider.ShortPeriodAttendance; }
            set
            {
                _raider.ShortPeriodAttendance = value;
                OnPropertyChanged("ShortPeriodAttendance");
                OnPropertyChanged("Attendance");
            }
        }

        public double ShortPeriodAllAttendance
        {
            get { return _raider.ShortPeriodAllAttendance; }
            set
            {
                _raider.ShortPeriodAllAttendance = value;
                OnPropertyChanged("ShortPeriodAllAttendance");
            }
        }

        public double LongPeriodAttendance
        {
            get { return _raider.LongPeriodAttendance; }
            set
            {
                _raider.LongPeriodAttendance = value;
                OnPropertyChanged("LongPeriodAttendance");
                OnPropertyChanged("Attendance");
            }
        }

        public double LongPeriodAllAttendance
        {
            get { return _raider.LongPeriodAllAttendance; }
            set
            {
                _raider.LongPeriodAllAttendance = value;
                OnPropertyChanged("LongPeriodAllAttendance");
            }
        }

        public double LifetimeAttendance
        {
            get { return _raider.LifetimeAttendance; }
            set
            {
                _raider.LifetimeAttendance = value;
                OnPropertyChanged("LifetimeAttendance");
            }
        }

        public double LifetimeAllAttendance
        {
            get { return _raider.LifetimeAllAttendance; }
            set
            {
                _raider.LifetimeAllAttendance = value;
                OnPropertyChanged("LifetimeAllAttendance");
            }
        }

        public int ShortPeriodLootCount
        {
            get { return _raider.ShortPeriodLootCount; }
            set
            {
                _raider.ShortPeriodLootCount = value;
                OnPropertyChanged("ShortPeriodLootCount");
            }
        }

        public double ShortPeriodLootValue
        {
            get { return _raider.ShortPeriodLootValue; }
            set
            {
                _raider.ShortPeriodLootValue = value;
                OnPropertyChanged("ShortPeriodLootValue");
            }
        }

        public double ShortPeriodLootRate
        {
            get { return _raider.ShortPeriodLootRate; }
            set
            {
                _raider.ShortPeriodLootRate = value;
                OnPropertyChanged("ShortPeriodLootRate");
            }
        }

        public double ShortPeriodWeightedLootRate
        {
            get { return _raider.ShortPeriodWeightedLootRate; }
            set
            {
                _raider.ShortPeriodWeightedLootRate = value;
                OnPropertyChanged("ShortPeriodWeightedLootRate");
            }
        }

        public double? ShortPeriodLootPriority
        {
            get { return _raider.ShortPeriodLootPriority; }
            set
            {
                _raider.ShortPeriodLootPriority = value;
                OnPropertyChanged("ShortPeriodLootPriority");
                OnPropertyChanged("ShortPeriodRelativeLootPriority");
            }
        }

        public double? ShortPeriodRelativeLootPriority
        {
            get { return _raider.ShortPeriodRelativeLootPriorty; }
        }

        public int LongPeriodLootCount
        {
            get { return _raider.LongPeriodLootCount; }
            set
            {
                _raider.LongPeriodLootCount = value;
                OnPropertyChanged("LongPeriodLootCount");
            }
        }

        public double LongPeriodLootValue
        {
            get { return _raider.LongPeriodLootValue; }
            set
            {
                _raider.LongPeriodLootValue = value;
                OnPropertyChanged("LongPeriodLootValue");
            }
        }

        public double LongPeriodWeightedLootRate
        {
            get { return _raider.LongPeriodWeightedLootRate; }
            set
            {
                _raider.LongPeriodWeightedLootRate = value;
                OnPropertyChanged("LongPeriodWeightedLootRate");
            }
        }

        public double LongPeriodLootRate
        {
            get { return _raider.LongPeriodLootRate; }
            set
            {
                _raider.LongPeriodLootRate = value;
                OnPropertyChanged("LongPeriodLootRate");
            }
        }

        public double? LongPeriodLootPriority
        {
            get { return _raider.LongPeriodLootPriority; }
            set
            {
                _raider.LongPeriodLootPriority = value;
                OnPropertyChanged("LongPeriodLootPriority");
            }
        }

        public double? LongPeriodRelativeLootPriority
        {
            get { return _raider.LongPeriodRelativeLootPriorty; }
        }

        public int LifetimeLootCount
        {
            get { return _raider.LifetimeLootCount; }
            set
            {
                _raider.LifetimeLootCount = value;
                OnPropertyChanged("LifetimeLootCount");
            }
        }

        public double LifetimeLootValue
        {
            get { return _raider.LifetimeLootValue; }
            set
            {
                _raider.LifetimeLootValue = value;
                OnPropertyChanged("LifetimeLootValue");
            }
        }

        public double LifetimeWeightedLootRate
        {
            get { return _raider.LifetimeWeightedLootRate; }
            set
            {
                _raider.LifetimeWeightedLootRate = value;
                OnPropertyChanged("LifetimeWeightedLootRate");
            }
        }

        public double LifetimeLootRate
        {
            get { return _raider.LifetimeLootRate; }
            set
            {
                _raider.LifetimeLootRate = value;
                OnPropertyChanged("LifetimeLootRate");
            }
        }

        public double? LifetimeLootPriority
        {
            get { return _raider.LifetimeLootPriority; }
            set
            {
                _raider.LifetimeLootPriority = value;
                OnPropertyChanged("LifetimeLootPriority");
            }
        }

        public double? LifetimeRelativeLootPriority
        {
            get { return _raider.LifetimeRelativeLootPriorty; }
        }

        public AttendanceLevel AttendanceLevel
        {
            get { return _raider.AttendanceLevel; }
        }

        public double AverageILevel
        {
            get { return _raider.AverageILevel; }
            set
            {
                _raider.AverageILevel = value;
                OnPropertyChanged("AverageILevel");
            }
        }

        public double GearScore
        {
            get { return _raider.GearScore; }
            set
            {
                _raider.GearScore = value;
                OnPropertyChanged("GearScore");
            }
        }

        public int ProgressionItemsCount
        {
            get { return _raider.ProgressionItemsCount; }
            set
            {
                _raider.ProgressionItemsCount = value;
                OnPropertyChanged("ProgressionItemsCount");
            }
        }

        public int LowLevelItemsCount
        {
            get { return _raider.LowLevelItemsCount; }
            set
            {
                _raider.LowLevelItemsCount = value;
                OnPropertyChanged("LowLevelItemsCount");
            }
        }

        #endregion

        #region Raider History

        // todo push this down a layer so we can use it elsewhere (e.g. for making form posts).

        public IEnumerable<RaiderRaidHistoryLine> RaidingHistory
        {
            get
            {
                if (_raidingHistory == null)
                {
                    _raidingHistory = new List<RaiderRaidHistoryLine>();

                    int lastRaidWeek = RaidHistory.LatestRaidWeek;
                    int firstHistoryWeek = Math.Max(1, lastRaidWeek - Options.LongPeriodLength);

                    IEnumerable<Raid> raidHistory = from r in RaidHistory.Raids
                                                    where r.Date >= _raider.Joined
                                                    orderby r.Date descending
                                                    select r;

                    raidHistory.ForEach(r =>
                    {
                        var historyLine = new RaiderRaidHistoryLine
                                              {
                                                  Date = r.Date,
                                                  Optional = r.IsOptional,
                                                  Attended = r.RaiderAttendedAnyChar(_raider.MainName) || r.IsExtraAttendee(_raider.Identity),
                                                  Location = r.Zone,
                                                  LootWon = null
                                              };

                        if (historyLine.Attended)
                        {
                            historyLine.LootWon = from l in r.Loot
                                                  where l.Winner == _raider.Identity
                                                  select l.Item;
                        }

                        _raidingHistory.Add(historyLine);
                    });
                }

                return _raidingHistory;
            }
        }

        private void ResetRaidingHistory()
        {
            _raidingHistory = null;
            OnPropertyChanged("RaidingHistory");
        }

        #endregion

        #region Raider Gear

        public int iLevelBase
        {
            get { return Options.iLevelBase; }
        }

        public ICommand DownloadArmouryDataCommand
        {
            get
            {
                if (_downloadArmouryData == null)
                {
                    _downloadArmouryData = new DelegateCommand(DownloadArmouryData);
                }

                return _downloadArmouryData;
            }
        }

        private void DownloadArmouryData()
        {
            // todo need to check this is a main?
            ArmouryService.EnqueueGetCharacter(Options.Region, Options.Realm, Name);
            ArmouryService.RunArmouryServiceAsync();
        }

        public ICommand SelectItemCommand
        {
            get
            {
                if (_selectItemCommand == null)
                {
                    _selectItemCommand = new DelegateCommand<LootItem>(SelectItem);
                }

                return _selectItemCommand;
            }
        }

        public ICommand SelectItemBottomSlotCommand
        {
            get
            {
                if (_selectItemBottomSlotCommand == null)
                {
                    _selectItemBottomSlotCommand = new DelegateCommand<LootItem>(SelectItemBottomSlot);
                }

                return _selectItemBottomSlotCommand;
            }
        }

        public void SelectItem(LootItem newItem)
        {
            if (newItem.ID == -1) return;

            var slot = newItem.Slot;
            LootItem oldItem = GetEquippedItem(slot);

            // If equipping a one handed weapon, check if we are unequipping a two hander
            if (slot == LootSlot.OneHand || slot == LootSlot.MainHand || slot == LootSlot.OffHand)
            {
                var oldTwoHander = GetEquippedItem(LootSlot.TwoHand);
                if (oldTwoHander != null)
                {
                    oldItem = oldTwoHander;
                }
            }

            // Equipping a two hander, old item can be a one hand or a main hand.
            if (slot == LootSlot.TwoHand && oldItem == null)
            {
                oldItem = GetEquippedItem(LootSlot.MainHand);
                if (oldItem == null) oldItem = GetEquippedItem(LootSlot.OneHand);
            }

            SelectItemInSlot(slot, newItem, oldItem);
        }

        public void SelectItemBottomSlot(LootItem newItem)
        {
            if (newItem.ID == -1) return;
            SelectItemInSlot(newItem.Slot, newItem, GetEquippedItem(newItem.Slot, false));
        }

        public void SelectItem(LootSlot slot)
        {
            SelectItemInSlot(slot, null, null);
        }

        private void SelectItemInSlot(LootSlot slot, LootItem newItem, LootItem oldItem)
        {
            if (newItem != null)
            {
                if (oldItem == null || newItem.Slot == LootSlot.TwoHand)
                {
                    Model.AutoEquip(newItem);

                    //
                }
                else 
                {
                    if (oldItem.Slot == LootSlot.TwoHand)
                    {
                        if (newItem.Slot != LootSlot.OffHand)
                        {
                            // Unequipping a twohander so auto equip an offhand.
                            AutoSelectItem(LootSlot.OffHand);

                            if (GetEquippedItem(LootSlot.OffHand) == null)
                            {
                                AutoSelectItem(LootSlot.OneHand);
                            }
                        }
                    }

                    Model.GearItems.Where(g => g.ID == newItem.ID).First().Equipped = true;
                    Model.GearItems.Where(g => g.ID == oldItem.ID).First().Equipped = false;
                }

                OnPropertyChanged("");
            }
            else
            {
                Model.AutoEquip(slot);
                RaisePropertyChangedFromSlot(slot);
            }
        }

        private void AutoSelectItem(LootSlot slot)
        {
            var thisSlot = from g in OwnedGear
                            where g.Loot.Slot == slot
                            select g;

            if (thisSlot.Count() == 0) return;

            var maxLevel = thisSlot.Max(g => g.Loot.iLevel);
            var bestGear = (from g in OwnedGear
                            where g.Loot.Slot == slot && !g.Offspec && g.Loot.iLevel == maxLevel
                            select g).FirstOrDefault();

            if (bestGear != null)
            {
                bestGear.Gear.Equipped = true;
                RaisePropertyChangedFromSlot(slot);
            }
        }

        private void RaisePropertyChangedFromSlot(LootSlot slot)
        {
            if (slot == LootSlot.Finger || slot == LootSlot.Trinket)
            {
                OnPropertyChanged(slot + "1Item");
                OnPropertyChanged(slot + "2Item");
            }
            else
            {
                OnPropertyChanged(slot + "Item");
            }
        }

        private List<GearViewModel> _ownedGear;

        public IEnumerable<GearViewModel> OwnedGear
        {
            get
            {
                if (_ownedGear == null)
                {
                    _ownedGear = (from l in Model.OwnedLoot
                                  join g in Model.GearItems on l.ID equals g.ID
                                  select new GearViewModel()
                                             {
                                                 Gear = g,
                                                 Loot = l
                                             }).ToList();

                    _ownedGear.ForEach(g => g.PropertyChanged += GearPropertyChanged);
                }

                return _ownedGear;
            }
        }

        public IEnumerable<GearViewModel> UnequippedGear
        {
            get { return OwnedGear.Where(g => !g.Gear.Equipped).OrderBy(g => g.Loot.iLevel); }
        }

        void GearPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var changedGear = sender as GearViewModel;

            if (e.PropertyName == "Offspec")
            {
                OnPropertyChanged("");
            }
            else if (e.PropertyName == "Equipped")
            {
                SelectItem(changedGear.Loot.Slot);
                OnPropertyChanged("");
            }
        }

        public IEnumerable<LootItem> Top3Gear
        {
            get { return Model.EquippedLoot.OrderByDescending(g => g.iLevel).Take(3); }
        }

        public IEnumerable<LootItem> Bottom3Gear
        {
            get { return Model.EquippedLoot.OrderBy(g => g.iLevel).Take(3); }
        }

        public LootItem HeadItem { get { return GetEquippedItem(LootSlot.Head); } }
        public IEnumerable<LootItem> HeadItems  { get { return GetOwnedItemsInSlot(LootSlot.Head); } }
        public IEnumerable<LootItem> UnequippedHeadItems  { get { return GetSlotChoices(LootSlot.Head); } }

        public LootItem NeckItem { get { return GetEquippedItem(LootSlot.Neck); } }
        public IEnumerable<LootItem> NeckItems { get { return GetOwnedItemsInSlot(LootSlot.Neck); } }
        public IEnumerable<LootItem> UnequippedNeckItems { get { return GetSlotChoices(LootSlot.Neck); } }

        public LootItem ShoulderItem { get { return GetEquippedItem(LootSlot.Shoulder); } }
        public IEnumerable<LootItem> ShoulderItems { get { return GetOwnedItemsInSlot(LootSlot.Shoulder); } }
        public IEnumerable<LootItem> UnequippedShoulderItems { get { return GetSlotChoices(LootSlot.Shoulder); } }

        public LootItem BackItem { get { return GetEquippedItem(LootSlot.Back); } }
        public IEnumerable<LootItem> BackItems { get { return GetOwnedItemsInSlot(LootSlot.Back); } }
        public IEnumerable<LootItem> UnequippedBackItems { get { return GetSlotChoices(LootSlot.Back); } }

        public LootItem ChestItem { get { return GetEquippedItem(LootSlot.Chest); } }
        public IEnumerable<LootItem> ChestItems { get { return GetOwnedItemsInSlot(LootSlot.Chest); } }
        public IEnumerable<LootItem> UnequippedChestItems { get { return GetSlotChoices(LootSlot.Chest); } }

        public LootItem WristItem { get { return GetEquippedItem(LootSlot.Wrist); } }
        public IEnumerable<LootItem> WristItems { get { return GetOwnedItemsInSlot(LootSlot.Wrist); } }
        public IEnumerable<LootItem> UnequippedWristItems { get { return GetSlotChoices(LootSlot.Wrist); } }

        public LootItem HandsItem { get { return GetEquippedItem(LootSlot.Hands); } }
        public IEnumerable<LootItem> HandsItems { get { return GetOwnedItemsInSlot(LootSlot.Hands); } }
        public IEnumerable<LootItem> UnequippedHandsItems { get { return GetSlotChoices(LootSlot.Hands); } }

        public LootItem WaistItem { get { return GetEquippedItem(LootSlot.Waist); } }
        public IEnumerable<LootItem> WaistItems { get { return GetOwnedItemsInSlot(LootSlot.Waist); } }
        public IEnumerable<LootItem> UnequippedWaistItems { get { return GetSlotChoices(LootSlot.Waist); } }

        public LootItem LegsItem { get { return GetEquippedItem(LootSlot.Legs); } }
        public IEnumerable<LootItem> LegsItems { get { return GetOwnedItemsInSlot(LootSlot.Legs); } }
        public IEnumerable<LootItem> UnequippedLegsItems { get { return GetSlotChoices(LootSlot.Legs); } }

        public LootItem FeetItem { get { return GetEquippedItem(LootSlot.Feet); } }
        public IEnumerable<LootItem> FeetItems { get { return GetOwnedItemsInSlot(LootSlot.Feet); } }
        public IEnumerable<LootItem> UnequippedFeetItems { get { return GetSlotChoices(LootSlot.Feet); } }

        public LootItem Finger1Item { get { return GetEquippedItem(LootSlot.Finger, true); } }
        public IEnumerable<LootItem> Finger1Items { get { return GetOwnedItemsInSlot(LootSlot.Finger); } }
        public LootItem Finger2Item { get { return GetEquippedItem(LootSlot.Finger, false); } }
        public IEnumerable<LootItem> Finger2Items { get { return GetOwnedItemsInSlot(LootSlot.Finger); } }
        public IEnumerable<LootItem> UnequippedFingerItems { get { return GetSlotChoices(LootSlot.Finger); } }

        public LootItem Trinket1Item { get { return GetEquippedItem(LootSlot.Trinket, true); } }
        public IEnumerable<LootItem> Trinket1Items { get { return GetOwnedItemsInSlot(LootSlot.Trinket); } }
        public LootItem Trinket2Item { get { return GetEquippedItem(LootSlot.Trinket, false); } }
        public IEnumerable<LootItem> Trinket2Items { get { return GetOwnedItemsInSlot(LootSlot.Trinket); } }
        public IEnumerable<LootItem> UnequippedTrinketItems { get { return GetSlotChoices(LootSlot.Trinket); } }

        public LootItem MainHandItem { get { return GetEquippedMainHandItem(); } }
        public IEnumerable<LootItem> MainHandItems { get { return GetMainHandWeaponChoices(); } }
        public IEnumerable<LootItem> UnequippedMainHandItems { get { return GetMainHandWeaponChoices(); } }

        public LootItem OffHandItem { get { return GetEquippedOffHandItem(); } }
        public IEnumerable<LootItem> OffHandItems { get { return GetOffHandWeaponChoices(); } }
        public IEnumerable<LootItem> UnequippedOffHandItems { get { return GetOffHandWeaponChoices(); } }

        public LootItem RangedItem { get { return GetEquippedRangedItem(); } }
        public IEnumerable<LootItem> RangedItems { get { return GetRangedWeaponChoices(); } }
        public IEnumerable<LootItem> UnequippedRangedItems { get { return GetRangedWeaponChoices(); } }

        private IEnumerable<LootItem> GetMainHandWeaponChoices()
        {
            var weapons = GetUnequippedMainHandWeapons();
            weapons = CheckForNoItems(weapons);

            return weapons;
        }

        private IEnumerable<LootItem> GetUnequippedMainHandWeapons()
        {
            return GetUnequippedThisSlot(LootSlot.MainHand).Union(GetUnequippedThisSlot(LootSlot.OneHand).Union(GetUnequippedThisSlot(LootSlot.TwoHand)));
        }

        private LootItem GetEquippedMainHandItem()
        {
            var weapon = GetEquippedItem(LootSlot.TwoHand);

            if (weapon != null)
            {
                // unequip offhand?    
                return weapon;
            }

            weapon = GetEquippedItem(LootSlot.MainHand);

            if (weapon == null)
            {
                weapon = GetEquippedItem(LootSlot.OneHand);
            }

            return weapon;
        }

        private IEnumerable<LootItem> GetOffHandWeaponChoices()
        {
            var weapons = GetUnequippedOffHandWeapons();
            weapons = CheckForNoItems(weapons);

            return weapons;

//            if (GetEquippedItem(LootSlot.TwoHand) != null)
//            {
//                // two hander so cannot equip off hand
//                return MakeDummyItemList();
//            }
//
//            // This is going to get confused with dual wield DPS and tanks?
//            var offHands = GetOwnedItemsInSlot(LootSlot.OffHand);
//            if (offHands.Count() > 0)
//            {
//                return offHands;
//            }
//
//            var oneHands = GetOwnedItemsInSlot(LootSlot.OneHand);
//            oneHands = CheckForNoItems(oneHands);
//
//            return oneHands;
        }

        private IEnumerable<LootItem> GetUnequippedOffHandWeapons()
        {
            return GetUnequippedThisSlot(LootSlot.OffHand).Union(GetUnequippedThisSlot(LootSlot.OneHand));
        }

        private LootItem GetEquippedOffHandItem()
        {
            if (GetEquippedItem(LootSlot.TwoHand) != null)
            {
                return null;
            }

            var weapon = GetEquippedItem(LootSlot.OffHand);
            if (weapon == null)
            {
                weapon = GetEquippedItem(LootSlot.OneHand, false);
            }

            return weapon;
        }

        private IEnumerable<LootItem> GetRangedWeaponChoices()
        {
            var unequippedThisSlot = GetUnequippedRangedWeapons();
            unequippedThisSlot = CheckForNoItems(unequippedThisSlot);

            return unequippedThisSlot;
        }

        private IEnumerable<LootItem> GetUnequippedRangedWeapons()
        {
            return
                GetUnequippedThisSlot(LootSlot.Ranged).Union(GetUnequippedThisSlot(LootSlot.Relic)).Union(
                    GetUnequippedThisSlot(LootSlot.Thrown));
        }

        private LootItem GetEquippedRangedItem()
        {
            var weapon = GetEquippedItem(LootSlot.Ranged);
            
            if (weapon == null)
            {
                weapon = GetEquippedItem(LootSlot.Relic);
            }

            if (weapon == null)
            {
                weapon = GetEquippedItem(LootSlot.Thrown);
            }

            return weapon;
        }

        private IEnumerable<LootItem> GetOwnedItemsInSlot(LootSlot slot)
        {
            return Model.OwnedLoot.Where(l => l.Slot == slot);
        }

        private IEnumerable<LootItem> GetSlotChoices(LootSlot slot)
        {
            var unequippedThisSlot = GetUnequippedThisSlot(slot);
            unequippedThisSlot = CheckForNoItems(unequippedThisSlot);

            return unequippedThisSlot;
        }

        private IEnumerable<LootItem> CheckForNoItems(IEnumerable<LootItem> unequippedThisSlot)
        {
            if (unequippedThisSlot.Count() == 0)
            {
                // todo fix this properly
                return MakeDummyItemList();
            }

            return unequippedThisSlot;
        }

        private IEnumerable<LootItem> MakeDummyItemList()
        {
            var dummyList = new List<LootItem>()
                                {
                                    new LootItem()
                                        {
                                            ID = -1,
                                            Name = "No other items available"
                                        }
                                };

            return dummyList;
        }

        private IEnumerable<LootItem> GetUnequippedThisSlot(LootSlot slot)
        {
            return from l in Model.OwnedLoot
                   join g in Model.GearItems on l.ID equals g.ID
                   where l.Slot == slot && !g.Equipped && !g.Offspec
                   select l;
        }

        private LootItem GetEquippedItem(LootSlot slot)
        {
            var equippedItem = GetEquippedItem(slot, true);
            return equippedItem;
        }

        private LootItem GetEquippedItem(LootSlot slot, bool firstItem)
        {
            var equippedThisSlot = from l in Model.OwnedLoot
                                   join g in Model.GearItems on l.ID equals g.ID
                                   where l.Slot == slot && g.Equipped
                                   select l;

            var count = equippedThisSlot.Count();
            if (count > 0)
            {
                var gear = equippedThisSlot.First();

                if (!firstItem)
                {
                    if (count == 1) return null; // No second slot item
                    gear = equippedThisSlot.Last();
                }

                return LootTable[gear.ID];
            }

            return null;
        }

        #endregion

        #region Events

        protected override void SubscribeEvents()
        {
            base.SubscribeEvents();

            RaidHistory.RaidAdded += RaidAdded;
            RaidHistory.RaidRemoved += RaidRemoved;
            RaidHistory.Raids.ForEach(r => r.AttendanceChanged += AttendanceChanged);

            Guild.CharacterDownloaded += GuildCharacterDownloaded;
            LootTable.ItemDownloaded += LootTableItemDownloaded;

            Calculator.Processed += OnDataProcessed;
        }

        protected override void UnsubscribeEvents()
        {
            base.UnsubscribeEvents();

            Calculator.Processed -= OnDataProcessed;
            LootTable.ItemDownloaded -= LootTableItemDownloaded;
            Guild.CharacterDownloaded -= GuildCharacterDownloaded;

            RaidHistory.Raids.ForEach(r => r.AttendanceChanged -= AttendanceChanged);
            RaidHistory.RaidRemoved -= RaidRemoved;
            RaidHistory.RaidAdded -= RaidAdded;

            if (_ownedGear != null)
            {
                _ownedGear.ForEach(g => g.PropertyChanged -= GearPropertyChanged);
            }
        }

        private void RaidRemoved(object sender, RaidsHistoryChangedEventArgs e)
        {
            e.Raid.AttendanceChanged += AttendanceChanged;
            ResetRaidingHistory();
            ResetLastRaidDate();
            OnPropertyChanged("");
        }

        private void RaidAdded(object sender, RaidsHistoryChangedEventArgs e)
        {
            e.Raid.AttendanceChanged += AttendanceChanged;
            ResetRaidingHistory();
            ResetLastRaidDate();
            OnPropertyChanged("");
        }

        private void OnDataProcessed()
        {
            OnPropertyChanged("");
        }

        void GuildCharacterDownloaded(object sender, CharacterDownloadedEventArgs e)
        {
            if (e.Name == Model.MainName)
            {
                OnPropertyChanged("Gear");
            }
        }

        void LootTableItemDownloaded(object sender, ItemDownloadedEventArgs e)
        {
            if (Model.HasGear(e.Id))
            {
                OnPropertyChanged("Gear");
                var item = LootTable[e.Id];
                if (item != null)
                {
                    Model.AutoEquip(item);
                    RaisePropertyChangedFromSlot(item.Slot);
                }
            }
        }

        private void AttendanceChanged(Raid raid)
        {
            ResetRaidingHistory();
        }

        #endregion
    }

    // todo merge/refactor these two classes?

    public class RaiderRaidHistoryLine
    {
        public DateTime Date { get; set; }
        public bool Attended { get; set; }
        public bool Optional { get; set; }
        public string Location { get; set; }
        public IEnumerable<LootItem> LootWon { get; set; }
    }

    public class RaiderRaidSummary
    {
        public DateTime Date { get; set; }
        public bool Optional { get; set; }
        public string Location { get; set; }
        public ObservableCollection<LootDropSummary> LootWon { get; set; }
    }

    public class LootDropSummary
    {
        public LootItem Item { get; set; }
        public double Value { get; set; }
        public string Breakdown { get; set; }
    }

    public class GearViewModel : INotifyPropertyChanged
    {
        public LootItem Loot { get; set; }
        public GearItem Gear { get; set; }

        public bool Offspec
        {
            get { return Gear.Offspec; }
            set
            {
                Gear.Offspec = value;

                if (Gear.Equipped && Gear.Offspec)
                {
                    Gear.Equipped = false;
                }

                OnPropertyChanged("Offspec");
                OnPropertyChanged("Equipped");
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}
